#!/usr/bin/env python
#Copyright (c) 2011 Isaac Dawson (WBTS Project)
#Permission is hereby granted, free of charge, to any person obtaining a copy 
#of this software and associated documentation files (the "Software"), to deal 
#in the Software without restriction, including without limitation the rights 
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
#copies of the Software, and to permit persons to whom the Software is furnished
#to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
#THE SOFTWARE.

import json
from glob import glob
import os
import socket
AF_INET6 = socket.AF_INET6

from twisted.application import service
from twisted.internet import reactor, defer
from twisted.python import components, log
from twisted.names import server, dns, client, resolve, common, authority
from twisted.names.dns import SimpleRecord, Record_MX, Record_AAAA, Record_A, Record_SOA
from twisted.web import xmlrpc
from zope.interface import Interface, implements

from wbts.dns.memory import MemoryAuthority
from utils import mgmt_utils

class IWBTSResolverService(Interface):
    def getAlternateDNS(self):
        """
        Returns the alternate server name and port value if an alternate server
        has been set in a dictionary object, None if one is not set.
        """
        return
    
    def setAlternateDNS(self, alt_server, alt_port):
        """
        Sets a client resolver for our service so we can look up records out of
        the authorities we control. Returns True on success False on failure.
        """
        return
    
    def getNamedRoot(self):
        """
        Returns the directory path that was configured to store our pyauthority
        files, returns None on error.
        """
        return
    
    def setNamedRoot(self, named_root, reload_authorities=True):
        """
        Sets the directory of our pyauthority files, and a boolean flag for if
        we wish to reload the authorities. True will reload where a value of
        False will not cause our DNS reload.
        """
        return
    
    def getAuthorities(self):
        """
        Returns the dictionary keys (each key being a string of an authority
        we control.) for our authorities.
        """
        return
    
    def removeAuthority(self, authority_name):
        """
        Completely removes the authority record from the DNS server. Causes
        DNS reload.
        """
        return
    
    def reloadAuthoritiesFromDisk(self):
        """
        Delete's all records and forces the server to re-read our records from
        our named root directory. Causes DNS reload.
        """
        return
    
    def getAuthorityRecordAsString(self, authority_name):
        """
        Returns a stringified representation of an authority or None if no
        authority with that name exists.
        """
        return
    
    def doesAuthorityExist(self, authority_name):
        """
        Takes an authority name and returns a True or False value depending on
        whether the authority exists.
        """
        return
    
    def doesRecordExistForAuthority(self, authority_name, record_type, hostname,
                                    *args):
        """
        Takes an authority by it's name, record type hostname, and *args specific
        to the record type and returns True if exists, False otherwise.
        """
        return
    
    def getAllAuthorities(self):
        """
        Returns an array of every authority and it's own sub-records. 
        """
    
    def getAuthorityRecord(self, authority_name):
        """
        Returns an entire authority record as a PyAuthorityInstance object or
        None if no record exists.
        """
        return
    
    def getRecordByIdAsDict(self, authority_name, rec_id):
        """
        Returns a record for an authority via it's record id. Returns an empty
        dict if not found
        """
        return
    
    def writeRecords(self):
        """
        Writes every authority in memory to disk with updated records. Returns
        False on the first invalid write attempt. True if all records were
        written successfully.
        """
        return
    
    def writeAuthorityRecord(self, authority_name):
        """
        Writes the authority to a file returns true/false
        """
        return

    
    def createRecordForAuthority(self, record_type, authority_name, hostname,
                                 record_id=None, *args):
        """
        Takes a type of record, the authority name a host name and *args specific
        to the record type. Causes DNS reload.
        Returns True if record is created successfully, False otherwise.
        """
        return
    
    def updateRecordForAuthority(self, rec_id, record_type, authority_name,
                                 hostname, *args):
        """
        Updates a record for an authority. Causes DNS reload.
        returns True on success, False otherwise.
        """
        return
            
    def removeRecordById(self, authority_name, rec_id):
        """
        Removes the record from authority_name. Causes DNS reload.
        returns True on success, False otherwise.
        """
        return
    
    def updateAuthority(self, authority_name, mname='', rname='', serial=0,
                        refresh=0, retry=0, expire=0, minimum=0):
        """
        Updates the authority with new SOA information. Causes DNS reload.
        Returns True on success, False otherwise.
        """
        return 
        
    def createAuthority(self, authority_name, mname='', rname='', serial=0,
                           refresh=0, retry=0, expire=0, minimum=0):
        """
        Creates a new authority for our dns server to manage. Causes DNS reload.
        Returns True on success, False otherwise.
        """
        return 
        
    def removeAuthority(self, authority_name, from_disk):
        """
        Removes the authority from the dns server. If from_disk is True, it will
        also have it's PySourceAuthority file removed. Causes DNS reload.
        Returns True on success, False otherwise.
        """
        return
    
    def getAllAuthoritiesAsDict(self):
        """
    Iterates over all self.mAuthorities, pulling out each record (SOA included)
    and pulls out the details of the record in a list of dict objects (1 dict
    object per authority)
        """
        return
    
    def getAuthorityRecordsAsDict(self, authority):
        """
    Takes either a string representation of the authority name, or an authority
    (PySourceAuthority/MemoryAuthority) and returns all records in a dict
    object.
        """
        return
    

class IWBTSDNSServerFactory(Interface):

    def updateAuthorities(self):
        """
        Causes the DNS Server to update it's authorities. Returns True.
        """
        return
    
    def setAlternateDNS(self, alt_server, alt_port):
        """
        Set's the alternate server and alternate port and calls
        updateAuthorities if successful. Returns False on invalid alternate
        port or server, True otherwise.
        """
        return
    

class WBTSDNSServerFactoryFromService(server.DNSServerFactory):
    implements(IWBTSDNSServerFactory)
    def __init__(self, service):
        """
        WBTSDNSServerFactoryFromService - implementation of our DNSServer
        The only really weird thing we are doing is setting a reference
        of ourself to the service. So from our management server, we can
        easily access the DNS factory. This makes it possible to modify
        or reload authorities in memory. The rest is pretty standard.
        """
        self.service = service
        self.verbose = 1
        self._updateAuthorities()
        
        # And finally, let's tell our service where we are...
        self.service.mDnsFactory = self
    
    def setAlternateDNS(self, alt_server, alt_port):
        if (isinstance(alt_server, str) != True or alt_server == ""):
            log.err("alt_server is invalid.")
            return False

        if (not mgmt_utils.is_port_valid(alt_port)):
            log.err("alt_port is invalid.")
            return False
        self.mAltPort = alt_port
        self.mAltServer = alt_server
        return self.updateAuthorities()
            
    def _updateAuthorities(self):
        self.caches = None
        self.clients = None
        self.resolvers = self.service.mAuthorities
        
        # just incase they updated the alternate DNS server while running
        # we'd want to reload that.
        if (self.service.mAltServer != ''):
            self.clients = [client.Resolver(servers=[
                (self.service.mAltServer, self.service.mAltPort)])
            ]        
        # Make it simple and re-initialize everything.
        server.DNSServerFactory.__init__(self, self.resolvers,
                                         self.caches, self.clients,
                                         self.verbose)
        return
        
    def updateAuthorities(self):
        self._updateAuthorities()
        log.msg("All authorities have now been reloaded.")       
        return True
    
components.registerAdapter(WBTSDNSServerFactoryFromService,
                           IWBTSResolverService,
                           IWBTSDNSServerFactory)


class WBTSResolverService(service.Service):
    implements(IWBTSResolverService)
    def __init__(self, service):
        """
        WBTSResolverService - Our service container for hosting the DNS
        Factory. Functionality related to changing records is found here.
        For now, only support for records found in "valid_records" is available.
        If you wish to add more record types add it to the above property and
        modify the createRecordOfType and authorityToPySourceAuthority.
        The service can talk to the factory because on instantiation of
        WBTSDNSServerFactoryFromService we assign this services dnsFactory to
        an instance of itself.
        """
        self.mNamedRoot = None
        self.mAltServer = None
        self.mAltPort = None
        self.mMgmtService = service
        self.mDnsFactory = None # this will be set by the factory on init
        self.mAuthorities = [] 
        self.mAuthorityDict = {}
        self.mAuthorityFiles = None # set in _loadFiles after settings are
                                    # gathered.
        self.mValidRecords = ["A","MX","SOA","NS","CNAME","AAAA"]
        self.mLockedRecords = [] # For rebind tests, see self.doRebind
        self._initConfigValues()
        
    #start startup up assignment
    def _initConfigValues(self):
        d = self.mMgmtService.getSettingCfgValues('DNSSettings')
        d.addCallback(self.setConfigValues)
        d.addErrback(lambda ret:
            log.err("Error getting dns values: %s"%ret))
        return d
    
    def setConfigValues(self, dns_values):
        self.mNamedRoot = dns_values['named_root']
        self.mAltPort = dns_values['alt_port']
        self.mAltServer = dns_values['alt_server']
        self._loadFiles()
        return
    
    def _loadFiles(self):
        self.mAuthorityFiles = self._readAuthorityDirectory()
        self._addAuthoritiesFromFiles()
        return

    def _addAuthoritiesFromFiles(self):
        """
        _addAuthoritiesFromFiles - takes in a list of files and creates a
        PySourceAuthority instance out of it. The self.mAuthorityDict key is
        set to the authority name, and the value for that key is the authority
        instance. The self.mAuthorities list is used by the DNSFactory but the
        dict points to the same instance member. dict objects are just easier
        to work with in most cases but twisted needs a list.
        @sets
        self.mAuthorityDict
        self.mAuthorities
        @calls
        createRecordIds - we need id's for when we update/delete records.    
        """
        # remove old values if they exist
        self.mAuthorities = []
        self.mAuthorityDict = {}
        for f in self.mAuthorityFiles:
            try:
                new_auth = authority.PySourceAuthority(f)
                self.createRecordIds(new_auth)
                soa_name = new_auth.soa[0]
                # we want it in a dict for easy access as well.
                self.mAuthorityDict[soa_name] = new_auth
                self.mAuthorities.append(new_auth)
            except IOError, e:
                log.err("ERROR reading authority file: %s\n%s"%(f,e))
                log.err("ERROR DNS will most likely be crippled.")
        
    def _readAuthorityDirectory(self):
        """
        _readAuthorityDirectory - reads all files ending in ".py" from the
        self.mNamedRoot property. 
        """
        authority_files = []
        try:
            authority_files = glob(self.mNamedRoot+"*.py")
            return authority_files
        except IOError, msg: 
            log.err("ERROR no authority files found in %s: %s"%self.mNamedRoot,
                    msg)
            log.err("ERROR DNS will most likely be crippled.")
        return authority_files

    def _writeFile(self, data, file_name):
        """
        _writeFile - internal method for writing the stringified authority
        data to a file in our "named_root"
        @params
        data - the string of our authority to write to disk
        file_name - the file to write the data to
        @returns
        True on success
        False on IOError
        """
        try:
            authority_file = open(file_name, 'w')
            authority_file.write(data)
            authority_file.close()
            return True
        except IOError, msg:
            log.err("ERROR writing %s to file: %s"%(file_name, msg))

        return False
    
    # start 'service' functions
    def getAlternateDNS(self):
        if (self.mAltPort == None or self.mAltServer == None):
            log.msg("Alternate DNS has not been set.")
            return None
        return {'alt_server':self.mAltServer, 'alt_port':self.mAltPort}
    
    def setAlternateDNS(self, alt_server, alt_port):
        return self.mDnsFactory.setAlternateDNS(alt_server, alt_port)

    def getNamedRoot(self):
        """
        getNamedRoot - Simple getter for named_root
        """
        return self.mNamedRoot

    def setNamedRoot(self, path, reload_authorities=True):
        """
        setNamedRoot - changes the directory for where we should
        read our PySourceAuthority dns files. It deletes references
        to the loaded authorities as they will be reset in
        _readAuthorityDirectory.
        @params
        path - the new path
        reload_authorities - pass False if you wish not to reload.
        """
        self.mNamedRoot = path
        if (reload_authorities == True):
            #self.mAuthorityFiles = self._readAuthorityDirectory()
            self.mAuthorities = [] # delete the old versions
            del self.mAuthorityDict # delete the old dictionary
            self.reloadAuthoritiesFromDisk()
        return

    def reloadAuthoritiesFromDisk(self):
        """
        reloadAuthoritiesFromDisk - re-reads the named_root directory and
        updates the dns server.
        @sets
        self.mAuthorityFiles
        @calls
        _readAuthorityDirectory
        _addAuthoritiesFromFiles
        dnsFactory.updateAuthorities
        @returns
        False if authorityfiles are empty
        True on success
        """
        self.mAuthorityFiles = self._readAuthorityDirectory()
        if (self.mAuthorityFiles == []):
            return False
        self._addAuthoritiesFromFiles()
        self.mDnsFactory.updateAuthorities()
        return True
                       
    def doesAuthorityExist(self, authority_name):
        """
        doesAuthorityExist - same check as getAuthorityRecord, but just returns
        True or False if the authority exists
        @params
        authority_name - the authority name to check (str)
        @returns
        True exists
        False doesn't exist.
        """
        try:
            self.mAuthorityDict[authority_name]
        except KeyError:
            return False
        return True
        
    def writeRecords(self):
        for authority_name,authority in self.mAuthorityDict.items():
            file_data = self.authorityToPySourceAuthority(authority)
            file_name = self.mNamedRoot+os.sep+authority_name+'.py'
            ret = self._writeFile(file_data, file_name)
            # bail out if even 1 record save fails.
            if (ret == False):
                log.err("There was a problem saving %s to disk."%authority_name)
                return False
        return True
                            
    def writeAuthorityRecord(self, authority_name):
        """
        writeAuthorityRecord - writes "authority_name" to disk. Internally calls
        authorityToPySourceAuthority to get the stringified version, then
        appends the named_root path with the authority name and .py to create
        the full file path.
        @params
        authority_name - A string of the authority to write to disk.
        @returns
        True on success
        False on Error (either authority doesn't exist or IOError when writing)
        """
        authority_record = None
        try:
            authority_record = self.mAuthorityDict[authority_name]
        except KeyError:
            log.err("ERROR Unable to find authority: %s"%authority_name)
            return False
        file_data = self.authorityToPySourceAuthority(authority_record)
        # SECURITY WARNING:
        # we never filter out / ' or .. from our authority names so
        # if you allow the mgmt service to be run on non-loopback interfaces
        # or an insecure network, you maybe an idiot.
        file_name = self.mNamedRoot+os.sep+authority_name+'.py'

        #we just let this block...
        return self._writeFile(file_data, file_name)
    
    def getAuthorities(self):
        """
        Simply returns our dictionary object of all our authorities.
        """
        return self.mAuthorityDict.keys()
        
    
    def removeAuthority(self, authority_name, from_disk=True):
        """
        removeAuthority - deletes the ENTIRE authority from memory and
        alternatively from disk.
        @sets
        delete's an item out of self.mAuthorityDict and self.mAuthorities
        @calls
        dnsFactory.updateAuthorities
        @returns
        False on OSError (unable to delete) or KeyError (authority doesn't
        exist)
        True on success.
        """
        try:
            # remove from our authority dictionary
            del self.mAuthorityDict[authority_name]
            # and from the actual authorities list used by the dnsFactory
            for idx in xrange(0, len(self.mAuthorities)):
                if (self.mAuthorities[idx].soa[0] == authority_name):
                    del self.mAuthorities[idx]
                    break
            # if we are removing from disk.
            if (from_disk == True):
                file_path = self.mNamedRoot + os.sep + authority_name + ".py"
                if (os.path.isfile(file_path) == True):
                    try:
                        log.msg("Removing %s from the system."%file_path)
                        os.remove(file_path)
                        
                    except OSError, msg:
                        log.err("ERROR removing authority file %s: %s"%(
                            file_path, msg))
                        return False
        except KeyError:
            log.err("ERROR Unable to remove %s, authority not found."%
                    authority_name)
            return False
        # since we modified self.mAuthorities, we need to reload the dnsFactory        
        self.mDnsFactory.updateAuthorities()
        log.msg("Successfully removed %s from the service."%authority_name)
        return True
            
        
    def createAuthority(self, authority_name, mname='', rname='', serial=0,
                           refresh=0, retry=0, expire=0, minimum=0):
        authority = self.getAuthorityRecord(authority_name)
        if (authority != None):
            log.err("ERROR Unable to create new authority, already exists.")
            return False
        # create new authority (this is our own type, see wbts.dns.authority)
        authority = MemoryAuthority()
        log.msg("Creating a new MemoryAuthority for: %s"%authority_name)
        #*args = []
        new_soa = self.createRecordOfType("SOA", mname, rname, serial, refresh,
                                          retry, expire, minimum)
        if (new_soa == None):
            log.err("ERROR Unable to create a record of SOA type.")
            return False
        # assign the authority the soa record along with our authority_name
        authority.soa = (authority_name, new_soa)
        # records are a dict with the key being the authority name
        # and the value being a list.
        authority.records[authority_name] = [new_soa]
        
        #add the new authority to our list and dict objects
        self.mAuthorities.append(authority)
        self.mAuthorityDict[authority_name] = authority
        log.msg("Updating DNS Factory with new Authority")
        self.mDnsFactory.updateAuthorities()
        return True

    def updateAuthority(self, authority_name, mname='', rname='', serial=0,
                           refresh=0, retry=0, expire=0, minimum=0):
        """
        Updates the authority with new SOA information. *args are variable for
        the SOA type. Looks up the authority by it's name, saves an old copy of
        the soa, then creates a new one based on the variable *args. Finally
        updates the factory with the new information.
        """
        authority = self.getAuthorityRecord(authority_name)
        if (authority == None):
            log.err("ERROR Unable to update %s as %s does not exist."\
                    (authority_name, authority_name))
            return False
        old_soa = authority.soa
        new_soa = self.createRecordOfType("SOA", mname, rname, serial, refresh,
                                          retry, expire, minimum)
        
        if (new_soa == None):
            log.err("ERROR Unable to create a record of SOA type.")
            return False
        authority.soa = (authority_name, new_soa)
        #try:
        for key, rec in self.nextRecordForAuthority(authority):
            # remove old and set new.
            if (rec.TYPE == dns.SOA):
                authority.records[key].remove(rec)
                authority.records[key].append(new_soa)
        
        log.msg("Reloading DNS factory with updated SOA record." )
        self.mDnsFactory.updateAuthorities()
        return True
        
    def createRecordForAuthority(self, record_type, authority_name,
                                 hostname, rec_id=None, *args):
        """
        createRecordForAuthority - Creates a new record for an authority.
        We first create a new record and pass in the variable *args,
        The authority is looked up by getAuthorityRecord. Since records is a
        dict object, we can just create the new key (the new_value) and assign
        it our new record. Then tell the factory to update itself.
        @params
        record_type - the record type, one of the self.valid_record types.
        authority_name - the authority we are adding this record to.
        hostname - the new hostname to create
        rec_id - The record id to supply (if None, will create a new one)
        *args - a variable arg list used when creating the records.
        @returns
        True on success
        False on failure
        """
        authority = self.getAuthorityRecord(authority_name)
        if (authority == None):
            log.err("ERROR Unable to create record for %s as %s does not exist."\
                    %(authority_name, authority_name))
            return False
        
        ret = self.doesRecordExist(authority, record_type, hostname, *args)
        if (ret == True):
            log.err("ERROR Unable to create record, similiar record already"\
                    "exists.")
            return False
        record = self.createRecordOfType(record_type, *args)
        if (record == None):
            log.err("ERROR creating record type: %s"%record_type)
            return False

        # if updating a record we will take the passed in record_id
        if (rec_id == None):
            record.id = self.getNextRecordId(authority)
        else:
            record.id = rec_id
            
        try:
            # a hostname value already defined.. so we just append a record.
            authority.records[hostname].append(record)
        except KeyError:
            # or it's an entirely new record
            authority.records[hostname] = [record]
        log.msg("Updating DNS Factory with new/updated record.")
        self.mDnsFactory.updateAuthorities()
        return True
    

    def updateRecordForAuthority(self, rec_id, record_type, authority_name,
                                 hostname, *args):
        """
        updateRecordForAuthority - some what of a hack, just delete the old
        record and create a new one.
        @params
        rec_id - the record id that we are replacing
        record_type - the type of record a/aaaa/cname/ns/mx...
        authority_name - the authority we are updating the record for.
        hostname - the hostname of the record
        *args - the various *args name/ttl/ip/pref etc..
        @returns
        False on unable to update
        True for success.
        """
        if (not isinstance(rec_id, int) or rec_id < 0):
            return False
        
        if (record_type not in self.mValidRecords):
            return False
        
        if (not isinstance(authority_name, str) or authority_name == ""):
            return False
        
        if (not isinstance(hostname, str) or hostname == ""):
            return False
        
        if ( (self.removeRecordById(authority_name, rec_id)) == False):
            return False
        
        return self.createRecordForAuthority(record_type, authority_name,
                                             hostname, rec_id, *args)


    def removeRecordById(self, authority_name, rec_id):
        authority = self.getAuthorityRecord(authority_name)
        if (authority == None):
            log.err("ERROR %s not found."%authority_name)
            return False
        
        record = self.getRecordById(authority, rec_id)
        if (record == None):
            log.err("ERROR record id: %d not found."%rec_id)
            return False
        
        for key, rec in self.nextRecordForAuthority(authority):
            if (rec.TYPE == dns.SOA):
                continue
            if (rec.id == rec_id):
                log.msg("Removing record %s"%rec)
                authority.records[key].remove(rec)

        log.msg("Reloading DNS factory with record removed.")
        self.mDnsFactory.updateAuthorities()
        return True
    
    def doesRecordExistForAuthority(self, authority_name, record_type, hostname,
                                    *args):
        """
        Get's the authority via authority_name and just calls doesRecordExist
        """
        authority = self.getAuthorityRecord(authority_name)
        return self.doesRecordExist(authority, record_type, hostname, *args)
        
    def doesRecordExist(self, authority, record_type, hostname, *args):
        """
        doesRecordExist - basically a sanity check. Make sure we don't
        have the same critical information in our authority already. For
        A/AAAA this means not having the same hostname and ip addr.
        For NS/CNAME/MX this means not having the same hostname and name.
        @params
        authority - The authority instance
        record_type - The type of record we are validating for existence
        hostname - The hostname of the record
        *args - The *args to the record we are comparing against.
        @returns
        True if a "similiar" record exists
        False if it doesn't..
        """
        ip = ttl = name = pref = None
        
        
        if (record_type == "A" or record_type == "AAAA"):
            ip,ttl = args
        elif(record_type == "NS" or record_type == "CNAME"):
            name,ttl = args
        elif(record_type == "MX"):
            name,pref,ttl = args
            
        for key,record_info in self.nextRecordForAuthority(authority):
            
            if (record_type == "A" and record_info.TYPE == dns.A):
                record_ip = socket.inet_ntoa(record_info.address)
                if (key == hostname and ip == record_ip):
                    return True
            elif(record_type == "AAAA" and record_info.TYPE == dns.AAAA):
                record_ip = socket.inet_ntop(AF_INET6, record_info.address)
                if (key == hostname and ip == record_ip):
                    return True
            elif(record_type == "NS" and record_info.TYPE == dns.NS):
                if (key == hostname and name == record_info.name):
                    return True
            elif(record_type == "CNAME" and record_info.TYPE == dns.CNAME):
                if (key == hostname and name == record_info.name):
                    return True
            elif(record_type == "MX" and record_info.TYPE == dns.MX):
                if (key == hostname and name == record_info.name):
                    return True
        return False
        
    def createRecordOfType(self, record_type, *args):
        """
        createRecordOfType - Used for inserting dns records on the fly
        into our dnsFactory. The record_type is determined, then a variable
        number of *args is sent to that records initialization. A new record
        is returned to the caller.
        @params
        record_type - must be one of the self.mValidRecords types.
        *args - the various *args required (ttl/name/ip/pref etc.)
        @returns
        the new record, or None
        """
        new_record = None
        try:
            if (record_type == "SOA"):
                # mname='', rname='', serial=0,
                # refresh=0, retry=0, expire=0, minimum=0
                new_record = dns.Record_SOA(*args)
            elif (record_type == "A"):
                # address='0.0.0.0', ttl=None
                new_record = dns.Record_A(*args)
            elif (record_type == "NS"):
                # name='', ttl=None
                new_record = dns.Record_NS(*args)
            elif (record_type == "MX"):
                # preference=0, name='', ttl=None,
                new_record = dns.Record_MX(*args)
            elif (record_type == "AAAA"):
                # address = '::', ttl=None
                new_record = dns.Record_AAAA(*args)
            elif (record_type == "CNAME"):
                # name='', ttl=None
                new_record = dns.Record_CNAME(*args)
        except ValueError, msg:
            log.err("ERROR failed to make record of %s due to: %s"%(record_type,
                                                                    msg))
            return None
        return new_record

    def getAuthorityRecord(self, authority_name):
        """
        getAuthorityRecord - takes in a authority_name (str) and returns the
        record associated with it.
        @params
        authority_name - the authority name to get the authority of (str)
        @returns
        The authority instance, or None if it doesn't exist.
        """
        try:
            return self.mAuthorityDict[authority_name]
        except KeyError:
            log.err("ERROR Unable to find authority: %s"%authority_name)
        return None

    def getAllAuthoritiesAsDict(self):
        result = []
        for authority in self.mAuthorities:
            authority_dict = self.getAuthorityRecordsAsDict(authority)
            result.append(authority_dict)
        return result
    
    def getAuthorityRecordsAsDict(self, authority):
        """
    Takes either a string representation of the authority name, or an authority
    (PySourceAuthority/MemoryAuthority) and returns all records in a dict
    object.
        """
        if (isinstance(authority, str)):
            authority = self.getAuthorityRecord(authority)
        else:
            return {}
        
        if (authority == None):
            log.msg("Could not find authority.")
            return {}
        
        result = {}
        for hostname,records in authority.records.items():
            result[hostname] = []
            for record in records:
                record_data = {}
                record_data = self.convertRecordToDict(record, hostname)
                result[hostname].append(record_data)
        return result
    
    def authorityToPySourceAuthority(self, authority):
        """
        authorityToPySourceAuthority - Returns the authority in a string format
        of a PySourceAuthority file.
        @params
        authority - the authority to parse out into a PySourceAuthority string.
        @returns
        A string of the authority records in PySourceAuthority format.
        """
        soa_name = authority.soa[0]
        soa = authority.soa[1]
        records = authority.records
        authorityfile = """zone = [
        SOA('%s',
            mname = '%s',
            rname = '%s',
            serial = %d,
            refresh = '%s',
            retry = '%s',
            expire = '%s',
            minimum = '%s'
        ),
        """%(soa_name,soa.mname,soa.rname,soa.serial, soa.refresh, soa.retry,
             soa.expire, soa.minimum)
    
        for key, record_info in self.nextRecordForAuthority(authority):
            ttl = record_info.ttl
            # if no ttl set it to soa's expire
            if (ttl == None):
                ttl = soa.expire
            if (record_info.TYPE == dns.A):
                addr = socket.inet_ntoa(record_info.address)
                authorityfile += "A('%s', '%s', %d),\r\n"%(key, addr, ttl)
            elif(record_info.TYPE == dns.AAAA):
                addr = socket.inet_ntop(AF_INET6, record_info.address)
                authorityfile += "AAAA('%s','%s', %d),\r\n"%(key, addr, ttl)
            elif(record_info.TYPE == dns.CNAME):
                authorityfile += "CNAME('%s','%s', %d),\r\n"%(key,
                                                         record_info.name,
                                                         ttl)
            elif(record_info.TYPE == dns.MX):
                authorityfile += "MX('%s', %d, '%s', %d),\r\n"%(key,
                                               record_info.preference,
                                               record_info.name,
                                                           ttl)
            elif(record_info.TYPE == dns.NS):
                authorityfile += "NS('%s','%s', %d),\r\n"%(key,
                                                      record_info.name,
                                                      ttl)
                
                    
        # remove the trailing ,
        authorityfile = authorityfile[0:authorityfile.rfind(',')]
        authorityfile += "\r\n]"
        return authorityfile

    
    def createRecordIds(self, authority):
        """
        createRecordIds - takes in a authority loops through the records
        and attachs an id to it. This makes it easy to figure out which
        record we are updating or deleteing from the various interfaces.
        """
        rec_id = 0
        #records = authority.records
        for key, record_info in self.nextRecordForAuthority(authority):
            # we only allow one SOA anyways so don't even bother
            # giving it an id
            if (record_info.TYPE == dns.SOA):
                continue
            record_info.id = rec_id
            rec_id += 1

    def getNextRecordId(self, authority):
        """
        getNextRecordId - loops over all records to get the largest id,
        then returns the next value.
        @params
        authority - the authority to get the next record id from
        @returns
        last_id - the next id to be used.
        """
        records = authority.records
        last_id = 0
        for key in records:
            record_list = records[key]
            for record_info in record_list:
                if (record_info.TYPE == dns.SOA):
                    continue
                # last id is less than record
                if (last_id < record_info.id):
                    # is there a gap between records? last=1 record=3
                    # if so, return last_id+1, otherwise assign last_id to
                    # the current id and keep going
                    if ((record_info.id - last_id) > 1):
                        return last_id+1
                    last_id = record_info.id

        return last_id+1
    
    def getRecordById(self, authority, rec_id):
        for key, rec in self.nextRecordForAuthority(authority):
            # WE SKIP SOA RECORDS!!!!
            if (rec.TYPE == dns.SOA):
                continue
            if (rec.id == rec_id):
                return rec
        return None

    def getRecordByIdAsDict(self, authority_name, rec_id):
        if (isinstance(authority_name, str)):
            authority = self.getAuthorityRecord(authority_name)
        else:
            return {}
        
        if (authority == None):
            log.msg("Could not find authority: %s"%authority_name)
            return {}
        print authority.records.items()
        print authority.records.keys()
        print authority.records.values()
        for hostname,records in authority.records.items():
            for record in records:
                if (hasattr(record, 'id')):
                    if (record.id == rec_id):
                        return self.convertRecordToDict(record, hostname)
        log.msg("Could not find record with the supplied ID: %d"%rec_id)
        return {}
    
    def convertRecordToDict(self, record, hostname):
        record_data = None
        if (isinstance(record, SimpleRecord)):
            #print "SIMPLE RECORD"
            record_data = {'record_type': record.fancybasename,
                           'hostname': hostname,
                           'rec_id': record.id,
                           'name': str(record.name),
                           'ttl': record.ttl}
        elif (isinstance(record, Record_A)):
            #print "A RECORD"
            record_data = {'record_type': 'A',
                           'hostname': hostname,
                           'rec_id': record.id,
                           'address':record.dottedQuad(),
                           'ttl':record.ttl}
        elif (isinstance(record, Record_AAAA)):
            #print "AAAA Record"
            record_data = {'record_type': 'AAAA',
                           'hostname': hostname,
                           'rec_id': record.id,
                           'address':record._address,
                           'ttl':record.ttl}
        elif (isinstance(record, Record_MX)):
            #print "MX RECORD"
            record_data = {'record_type': record.fancybasename,
                           'hostname': hostname,
                           'rec_id': record.id,
                           'name': str(record.name),
                           'preference': record.preference,
                           'ttl': record.ttl}
        elif (isinstance(record, Record_SOA)):
            #print "SOA RECORD"
            # SOA records do not have id's associated with them.
            record_data = {'record_type': 'SOA',
                           'mname': str(record.mname),
                           'rname': str(record.rname),
                           'serial': record.serial,
                           'refresh': record.refresh,
                           'retry': record.retry,
                           'minimum': record.minimum,
                           'expire': record.expire
                           }
        return record_data
        
    def nextRecordForAuthority(self, authority):
        for key in authority.records:
            record_list = authority.records[key]
            for record_info in record_list:
                yield (key, record_info)
    
    def _revertRebindValue(self, record, orig_value, type):
        self.mLockedRecords.remove(record)
        if (type == 'addr'):
            record.address = orig_value
        else:
            record.name = orig_value
        return
    
    def doRebind(self, authority, host, rec_type, new_value, delay):
        """
        doRebind first verfies we have a record to update. Authority is the
        domain: attacker.com  host is the 'record': www.attacker.com
        rec_type is the type: CNAME
        new_value is what we will update to: www.victim.com
        delay is when we will flip the record back to www.attacker.com
        We create a list of our rebinding records, this is to prevent
        multiple requests destroying the original value.
        """
        new_record_info = None
        authority_record = self.getAuthorityRecord(authority)
        for record_key, record_info in self.nextRecordForAuthority(authority_record):
            # loop until we find the correct record type
            if(dns.QUERY_TYPES.get(record_info.TYPE) == rec_type):
                # for the correct record type, is the given host equal
                # to the record?
                if (record_key == host):
                    # has our original delay not been called yet?
                    if (record_info in self.mLockedRecords):
                        log.msg("doRebind called on a record "\
                                " that is already locked, returning.")
                        return False
                    # it is, so do we have an A record?
                    if (record_info.TYPE == dns.A):
                        try:
                            addr = socket.inet_aton(new_value)
                            reactor.callLater(delay,
                                              self._revertRebindValue,
                                              record_info,
                                              record_info.address,
                                              'addr')
                            record_info.address = addr
                            self.mLockedRecords.append(record_info)
                            break
                        except ValueError, msg:
                            log.err("Unable to convert supplied value to "\
                                    "an ipv4 address: %s"%msg)
                        return False
                    # or an AAAA record?
                    elif (record_info.TYPE == dns.AAAA):
                        try:
                            addr = socket.inet_pton(AF_INET6, new_value)
                            reactor.callLater(delay,
                                              self._revertRebindValue,
                                              record_info,
                                              record_info.address,
                                              'addr')
                            record_info.address = addr
                            self.mLockedRecords.append(record_info)
                            break
                        except ValueError, msg:
                            log.err("Unable to convert supplied value to "\
                                    "an ipv6 address: %s"%msg)
                        return False
                    # no it's some other kinda record (CNAME etc.)
                    else:
                        reactor.callLater(delay,
                                          self._revertRebindValue,
                                          record_info,
                                          record_info.name,
                                          'name')
                        record_info.name = dns.Name(new_value)
                        self.mLockedRecords.append(record_info)
                        break
        return True
        
        
class WBTSResolverXR(xmlrpc.XMLRPC):
    """
    XMLRPC interface found at: 
    """
    def __init__(self, service):
        xmlrpc.XMLRPC.__init__(self, allowNone=True)
        self.mService = service
        self.putSubHandler('system', xmlrpc.XMLRPCIntrospection(self))

    def xmlrpc_getAlternateDNS(self):
        return self.mService.getAlternateDNS()
        
    def xmlrpc_setAlternateDNS(self, alt_server, alt_port):
        return self.mService.setAlternateDNS(alt_server, alt_port)
    
    def xmlrpc_getNamedRoot(self):
        return self.mService.getNamedRoot()
    
    def xmlrpc_setNamedRoot(self, named_root, reload_authorities=True):
        return self.mService.setNamedRoot(named_root, reload_authorities)

    def xmlrpc_reloadAuthoritiesFromDisk(self):
        return self.mService.reloadAuthoritiesFromDisk()

    def xmlrpc_doesAuthorityExist(self, authority_name):
        return self.mService.doesAuthorityExist(authority_name) 

    def xmlrpc_getAuthorities(self):
        return self.mService.getAuthorities()
    
    def xmlrpc_doesRecordExistForAuthority(self, authority_name, record_type,
                                           hostname, *args):
        return self.mService.doesRecordExistForAuthority(authority_name,
                                                         record_type,
                                                         hostname,
                                                         *args)
    
    def xmlrpc_getAuthorityRecord(self, authority_name):
        return self.mService.getAuthorityRecord(authority_name)
    
    def xmlrpc_writeAuthorityRecord(self, authority_name):
        return self.mService.writeAuthorityRecord(authority_name)
        
    def xmlrpc_writeRecords(self):
        return self.mService.writeRecords()

    def xmlrpc_getRecordByIdAsDict(self, authority_name, rec_id):
        return self.mService.getRecordByIdAsDict(authority_name, rec_id)
    
    
    def xmlrpc_updateRecordForAuthority(self, rec_id, record_type,
                                        authority_name, hostname, *args):
        return self.mService.updateRecordForAuthority(rec_id, record_type,
                                                      authority_name, hostname,
                                                      *args)
        
    def xmlrpc_createRecordForAuthority(self, record_type, authority_name,
                                        hostname, *args):
        return self.mService.createRecordForAuthority(record_type,
                                                      authority_name, hostname,
                                                      None, *args)
    
    
    def xmlrpc_removeRecordById(self, authority_name, rec_id):
        return self.mService.removeRecordById(authority_name, rec_id)
    
    def xmlrpc_createAuthority(self, authority_name, mname='', rname='',
                               serial=0, refresh=0, retry=0, expire=0,
                               minimum=0):
        
        return self.mService.createAuthority(authority_name, mname, rname,
                                             serial, refresh, retry, expire,
                                             minimum)
    
    def xmlrpc_updateAuthority(self, authority_name, mname='', rname='',
                               serial=0, refresh=0, retry=0, expire=0,
                               minimum=0):
        
        return self.mService.updateAuthority(authority_name, mname, rname,
                                             serial, refresh, retry, expire,
                                             minimum)
        
    
        
    def xmlrpc_removeAuthority(self, authority_name, from_disk=True):
        """
        Completely removes the authority record from the DNS server. If you wish
        to only remove from memory, pass False for from_disk.
        """
        return self.mService.removeAuthority(authority_name, from_disk)
        
    def xmlrpc_getAllAuthoritiesAsDict(self):
        return self.mService.getAllAuthoritiesAsDict()
    
    def xmlrpc_getAuthorityRecordsAsDict(self, authority_name):
        return self.mService.getAuthorityRecordsAsDict(authority_name)
        